﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Datamining.Models;

namespace Datamining.Program
{
    public class SlopeOne
    {
        public Dictionary<int, double> getPrediction(int userid)
        {
            UserPreferences currentUser = Program.userRatingDictionary[userid];
            int[] notRatedByUser = currentUser.GetArticlesNotRatedByUser().Except(new int[] { 0 }).ToArray();
            int[] ratedByUser = currentUser.getItemIdArray();
            
            // Key == itemid, value == predictionvalue
            Dictionary<int, double> resultset = new Dictionary<int, double>();
            
            // Loop door alle items die de user niet gerate heeft.
            foreach (int itemINoPref in notRatedByUser)
            {
                // Houdt een som bij voor de euclidean distance per item die de user niet gerate heeft.
                double som_product = 0;
                double som_factoor = 0;
                // Loop door alle items die de user wel heeft gerate.
                foreach (int itemJPref in ratedByUser)
                {
                    // De combinatie item 101 en 102 is hetzelfde als 102 en 101.
                    // Dit zorgt ervoor dat wanneer de combinatie 102 en 101 voorkomt hij de waardes omwisselt naar 101 en 102 zodat het opgehaalt kan worden.
                    // Heeft te maken met de triangle array.
                    int i = (itemINoPref > itemJPref) ? itemJPref : itemINoPref;
                    int j = (itemINoPref > itemJPref) ? itemINoPref : itemJPref;
                    // Controleer of de sleutels wel voorkomen.
                    if (Program.slopeOneDifference.ContainsKey(i) && Program.slopeOneDifference[i].ContainsKey(j))
                    {
                        // Haalt uit de voor gecalculeerde waardes de juiste op en voeg deze samen met de user rating.
                        double eucl = Program.slopeOneDifference[i][j];
                        double userRatingItemJ = currentUser.getRatingByItemId(itemJPref);
                        som_factoor += eucl;
                        som_product += eucl * userRatingItemJ;
                    }
                }
                double euclResult = som_product / som_factoor;
                resultset.Add(itemINoPref, euclResult);
            }

            // Geeft de beste 10 items id + predictie waarde terug.
            return resultset.OrderByDescending(x => x.Value).Take(10).ToDictionary(x => x.Key, x => x.Value);
        }

        public Dictionary<int, Dictionary<int, double>> preProcessSlopeOne()
        {
            // houdt een lijst bij van items die al berekent zijn.
            List<int> usedItems = new List<int>();
            Dictionary<int, Dictionary<int, double>> dic_gemiddelde_verschil = new Dictionary<int, Dictionary<int, double>>();            

            // Loop door alle items die er zijn.
            foreach (int itemIdI in Program.itemDictionary.Keys)
            {
                // Loop door alle items behalve itemI.
                usedItems.Add(itemIdI);
                int[] allItemsExceptItemIDI = Program.itemDictionary.Keys.Except(usedItems).ToArray();
                foreach (int itemIdJ in allItemsExceptItemIDI)
                {
                    // Haal alle users op die zowel item I als item J hebben gerate.
                    int[] userIDsRatedBothItems = getArrayUsersBothItemsPreferences(itemIdI, itemIdJ);
                    if (userIDsRatedBothItems.Length > 0)
                    {
                        // Loop door alle users die de items hebben gerate en sla die in de som euclidean op.
                        double somEuclidean = 0;
                        foreach (int user in userIDsRatedBothItems)
                        {
                            double prefI = Program.userRatingDictionary[user].getRatingByItemId(itemIdI);
                            double prefJ = Program.userRatingDictionary[user].getRatingByItemId(itemIdJ);
                            somEuclidean += Math.Pow((prefI - prefJ), 2);
                        }
                        double euclideanDistance = 1/(1 + (Math.Sqrt(somEuclidean)));
                        // Als ItemI nog niet in de dictionary voorkomt dan nieuwe aanmaken en opslaan.
                        // Als die wel bestaat dan toevoegen.
                        if (!dic_gemiddelde_verschil.ContainsKey(itemIdI))
                        {
                            Dictionary<int, double> value = new Dictionary<int, double>();
                            value.Add(itemIdJ, euclideanDistance);
                            dic_gemiddelde_verschil.Add(itemIdI, value);
                        }
                        else
                        {
                            Dictionary<int, double> temp = dic_gemiddelde_verschil[itemIdI];
                            temp.Add(itemIdJ, euclideanDistance);
                        }
                    }
                }
                Console.WriteLine("ItemI met ID: " + itemIdI + " gedaan.");
            }
            return dic_gemiddelde_verschil;
        }

        // Update de desbetreffende itemID en alle waardes die erbij horen.
        public void updateSlopeOneRating(int itemIdI)
        {
            int[] allItemsExceptItemIDI = Program.itemDictionary.Keys.Except(new int[] { itemIdI }).ToArray();

            foreach (int itemIdJ in allItemsExceptItemIDI)
            {
                int[] userIDsRatedBothItems = getArrayUsersBothItemsPreferences(itemIdI, itemIdJ);
                if (userIDsRatedBothItems.Length > 0)
                {
                    double somEuclidean = 0;
                    foreach (int user in userIDsRatedBothItems)
                    {
                        double prefI = Program.userRatingDictionary[user].getRatingByItemId(itemIdI);
                        double prefJ = Program.userRatingDictionary[user].getRatingByItemId(itemIdJ);
                        somEuclidean += Math.Pow((prefI - prefJ), 2);
                    }
                    double euclideanDistance = 1 / (1 + (Math.Sqrt(somEuclidean)));

                    // Als itemI groter is dan itemJ. Dus item 103 wordt geupdate en hij moet 101 updaten dan wordt op de plek
                    // 101 en 103 de euclidean waarde opgeslagen. Anders wordt het op 103 en 104 bijvoorbeeld opgeslagen.
                    if (itemIdI > itemIdJ)
                    {
                        Program.slopeOneDifference[itemIdJ][itemIdI] = euclideanDistance;
                    }
                    else
                    {
                        // j > i
                        if (!Program.slopeOneDifference.ContainsKey(itemIdI))
                        {
                            Dictionary<int, double> value = new Dictionary<int, double>();
                            value.Add(itemIdJ, euclideanDistance);
                            Program.slopeOneDifference.Add(itemIdI, value);
                        }
                        else
                        {
                            Program.slopeOneDifference[itemIdI][itemIdJ] = euclideanDistance;
                        }
                    }
                }
            }
        }

        // Geef een lijst terug van usersID die allemaal itemI en itemJ hebben gerate.
        private int[] getArrayUsersBothItemsPreferences(int itemIdI, int itemIdJ)
        {
            List<int> list = new List<int>();
            foreach (KeyValuePair<int, UserPreferences> user in Program.userRatingDictionary)
            {
                if (user.Value.getItemIdArray().Contains(itemIdI) && user.Value.getItemIdArray().Contains(itemIdJ))
                {
                    list.Add(user.Key);
                }
            }

            return list.ToArray();
        }
    }
}
